//
// Created by entaoyang on 2019-02-19.
// Copyright (c) 2019 yet.net. All rights reserved.
//

import Foundation
import CoreGraphics

#if os(iOS) || os(tvOS)
import UIKit
#else
import AppKit
#endif


public func htmlStr(_ font: Font, _ s: String) -> NSAttributedString {
    let ss = try? NSMutableAttributedString(data: s.dataUnicode, options: [
        NSAttributedString.DocumentReadingOptionKey.documentType: NSAttributedString.DocumentType.html
    ], documentAttributes: nil)
    if let sss = ss {
        sss.addAttribute(NSAttributedString.Key.font, value: font, range: NSRange(location: 0, length: sss.length))
        return sss
    }
    return NSAttributedString(string: s)
}

public extension NSAttributedString {

    func linkfy() -> NSMutableAttributedString {
        let s = NSMutableAttributedString(attributedString: self)

        guard let detect = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue | NSTextCheckingResult.CheckingType.phoneNumber.rawValue) else {
            return s
        }
        let ms = detect.matches(in: s.string, range: NSRange(location: 0, length: s.length))
        for m in ms {
            if m.resultType == .link, let url = m.url {
                s.addAttributes([.link: url,
                                 .foregroundColor: Color.blue,
                                 .underlineStyle: NSUnderlineStyle.patternDot.rawValue
                ], range: m.range)
            } else if m.resultType == .phoneNumber, let pn = m.phoneNumber {
                s.addAttributes([.link: pn,
                                 .foregroundColor: Color.blue,
                                 .underlineStyle: NSUnderlineStyle.patternDot.rawValue
                ], range: m.range)
            }
        }
        return s
    }

}

public typealias TextStyleMap = Map<NSAttributedString.Key, Any>
public typealias TextStyleDic = Dictionary<NSAttributedString.Key, Any>

public class StyledText {
    public var text: String
    public var map: TextStyleMap

    public var dic: TextStyleDic {
        map.dicValue
    }

    public func style(_ block: (TextStyleBuilder) -> Void) {
        let b = TextStyleBuilder(map: self.map)
        block(b)
    }

    public var paragraph: NSMutableParagraphStyle {
        if let p = map[.paragraphStyle] as? NSMutableParagraphStyle {
            return p
        } else {
            let p = NSMutableParagraphStyle()
            map[.paragraphStyle] = p
            return p
        }
    }

    @discardableResult
    public func remove(key: NSAttributedString.Key) -> Any? {
        map.removeValue(forKey: key)
    }

    public init(text: String, map: TextStyleMap) {
        self.text = text
        self.map = map
        if !map.contains(key: .font) {
            map[.font] = Font.systemFont(ofSize: Font.labelFontSize, weight: .medium)
        }
        if !map.contains(key: .backgroundColor) {
            map[.backgroundColor] = UIColor.clear
        }
        if !map.contains(key: .foregroundColor) {
            map[.foregroundColor] = UIColor.label
        }
        if !map.contains(key: .paragraphStyle) {
            map[.paragraphStyle] = NSMutableParagraphStyle().apply { p in
                p.lineBreakMode = .byWordWrapping
                p.lineBreakStrategy = .pushOut
            }
        }

    }

    public convenience init(text: String, dic: TextStyleDic) {
        self.init(text: text, map: Map(dic: dic))
    }

    public convenience init() {
        self.init(text: "", map: [:])
    }

    public subscript(key: NSAttributedString.Key) -> Any? {
        get {
            map[key]
        }
        set {
            map[key] = newValue
        }
    }
}

public func styledText(_ block: (AttributedTextBuilder) -> Void) -> NSAttributedString {
    let a = AttributedTextBuilder()
    block(a)
    return a.attributedText
}

public class AttributedTextBuilder {
    private var textList: List<StyledText> = List()

    public init() {
    }

    public var textStyleList: [StyledText] {
        textList.toArray
    }

    public var attributedText: NSAttributedString {
        let attredText = NSMutableAttributedString(string: textList.reduce("") { s, item in
            s + item.text
        })
        var offset: Int = 0
        for item in textList {
            let text = item.text
            attredText.setAttributes(item.dic, range: NSRange(location: offset, length: text.count))
            offset += text.count
        }
        return attredText
    }
    private var textJoined: String {
        textList.reduce("") { s, a in
            s + a.text
        }
    }

    @discardableResult
    public func append(_ text: String) -> NSRange {
        append(text, [:])
    }

    @discardableResult
    public func append(_ text: String, _ map: TextStyleDic) -> NSRange {
        let s = textJoined
        textList.append(StyledText(text: text, dic: map))
        return NSRange(location: s.count, length: text.count)
    }

    @discardableResult
    public func append(_ text: String, _ block: (TextStyleBuilder) -> Void) -> NSRange {
        let b = TextStyleBuilder()
        block(b)
        return append(text, b.map.dicValue)
    }
}


public class TextStyleBuilder {
    public let map: TextStyleMap

    public init(map: TextStyleMap = TextStyleMap()) {
        self.map = map
    }
}

public extension TextStyleBuilder {

    @discardableResult
    func paragraphStyle(_ block: (NSMutableParagraphStyle) -> Void) -> Self {
        let ps = (map[.paragraphStyle] as? NSMutableParagraphStyle) ?? NSMutableParagraphStyle()
        block(ps)
        map[.paragraphStyle] = ps
        return self
    }

    @discardableResult
    func link(_ url: URL) -> Self {
        map[.link] = url
        return self
    }

    @discardableResult
    func foreColor(_ color: Color) -> Self {
        map[.foregroundColor] = color
        return self
    }

    @discardableResult
    func backColor(_ color: Color) -> Self {
        map[.backgroundColor] = color
        return self
    }

    @discardableResult
    func underlineColor(_ color: Color) -> Self {
        map[.underlineColor] = color
        return self
    }

    @discardableResult
    func underlineStyle(_ u: NSUnderlineStyle) -> Self {
        map[.underlineStyle] = u.rawValue
        return self
    }


    @discardableResult
    func shadow(_ c: NSShadow) -> Self {
        map[.shadow] = c
        return self
    }

    @discardableResult
    func baseline(offset: CGFloat) -> Self {
        map[.baselineOffset] = offset
        return self
    }

    @discardableResult
    func font(_ c: Font) -> Self {
        map[.font] = c
        return self
    }

    @discardableResult
    func font(size: CGFloat, weight: Font.Weight) -> Self {
        font(Font.systemFont(ofSize: size, weight: weight))
    }

    @discardableResult
    func fontMono(size: CGFloat, weight: Font.Weight) -> Self {
        font(Font.monospacedSystemFont(ofSize: size, weight: weight))
    }

    @discardableResult
    func fontMonoDigit(size: CGFloat, weight: Font.Weight) -> Self {
        font(Font.monospacedDigitSystemFont(ofSize: size, weight: weight))
    }
}

